﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Net;
using System.Threading.Tasks;
using Devonline.Core;
using Devonline.Entity;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.DynamicLinq;
using Microsoft.Extensions.Caching.Distributed;
using Attachment = Devonline.Entity.Attachment;
using static Devonline.Core.Helper;

namespace Devonline.Http
{
    /// <summary>
    /// 数据库上下文扩展操作集合
    /// </summary>
    public static class DbContextExtensions
    {
        #region 数据库上下文基础方法
        /// <summary>
        /// 自动迁移
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public static async Task AutoMigrationAsync(this DbContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if ((await context.Database.GetPendingMigrationsAsync()).Any())
            {
                await context.Database.MigrateAsync();
            }
        }

        /// <summary>
        /// 统一数据源查询入口, 可缓存数据源
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="context"></param>
        /// <param name="enableCache"></param>
        /// <returns>返回对当前类型的数据库上下文的引用</returns>
        public static IQueryable<TEntitySet> GetQueryable<TEntitySet>(this DbContext context, bool enableCache = false) where TEntitySet : class
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var type = typeof(TEntitySet);
            if (type.HasAttribute<CacheableAttribute>() && enableCache)
            {
                var cacheKey = AppSettings.CACHE_DATA + type.Name.ToUpper(CultureInfo.CurrentCulture);
                var cache = context.GetNonPublicValue<IDistributedCache>();
                if (cache != null)
                {
                    var entitySets = cache.GetValue<IEnumerable<TEntitySet>>(cacheKey);
                    if (entitySets == null)
                    {
                        entitySets = context.Set<TEntitySet>().ToHashSet();
                        cache.SetValue(cacheKey, entitySets);
                    }

                    return entitySets.AsQueryable();
                }
            }

            return context.Set<TEntitySet>();
        }
        /// <summary>
        /// 统一数据源查询入口非泛型方式
        /// </summary>
        /// <param name="context"></param>
        /// <param name="type"></param>
        /// <param name="enableCache"></param>
        /// <returns></returns>
        public static IQueryable GetQueryable(this DbContext context, Type type, bool enableCache = false)
        {
            typeof(DbContextExtensions).InvokeGenericMethod(nameof(GetQueryable), context, new object[] { enableCache }, out object returnValue, type);
            return returnValue as IQueryable;
        }

        /// <summary>
        /// 从 QueryOptions 和上下文自动获取查询表达式的内容并执行查询返回分页的结果
        /// 暂不支持 orderby, select 和 expand 表达式
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="queryOptions"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public static async Task<PagedResult<T>> GetPagedResultAsync<T>(this DbContext context, QueryOptions<T> queryOptions) where T : class
        {
            var queryable = context.Set<T>().AsQueryable();
            if (queryOptions.Filter != null)
            {
                if (queryOptions.Filter is NestedFilterOption<T> filter && filter.Filters.IsNotNullOrEmpty())
                {
                    //目前仅支持一级嵌套, 且顶级条件仅支持 and
                    //var expression = string.Join(GetFilterLogic(filter.Logic ?? LogicType.And), filter.Filters.Select(x => GetFilterExpression(x)));
                    queryable = queryable.Where(string.Join(GetFilterLogic(filter.Logic ?? LogicType.And), filter.Filters.Select(x => GetFilterExpression(x))));
                }
                else if (queryOptions.Filter.Field.IsNotNullOrWhiteSpace() && queryOptions.Filter.Value.IsNotNullOrWhiteSpace())
                {
                    queryable = queryable.Where(GetFilterExpression(queryOptions.Filter), queryOptions.Filter.FieldValue);
                }
            }

            var result = new PagedResult<T>();
            if (queryOptions.Count.HasValue && queryOptions.Count.Value)
            {
                result.Total = await queryable.CountAsync();
            }

            if (queryOptions.Skip.HasValue && queryOptions.Skip.Value > 0)
            {
                queryable = queryable.Skip(queryOptions.Skip.Value);
            }

            if (queryOptions.Top.HasValue && queryOptions.Top.Value > 0)
            {
                queryable = queryable.Take(queryOptions.Top.Value);
                result.PageSize = queryOptions.Top.Value;
            }

            result.PageIndex = ((queryOptions.Skip ?? 1) - 1) / result.PageSize + 1;
            result.Rows = await queryable.ToListAsync();
            return result;
        }
        #endregion

        #region 字符串主键的默认实现
        /// <summary>
        /// 新增阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <returns></returns>
        public static Task UniqueAsync<TEntitySet>(this DbContext context, TEntitySet entitySet) where TEntitySet : class, IEntitySet<string> => context.UniqueAsync<TEntitySet, string>(entitySet);
        /// <summary>
        /// 单个数据对象的新增, 自动验证重复, 但不处理附件和引用集合
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static Task<TEntitySet> AddToSaveChangeAsync<TEntitySet>(this DbContext context, TEntitySet entitySet, string user = null) where TEntitySet : class, IEntitySet<string> => context.AddToSaveChangeAsync<TEntitySet, string>(entitySet, user);
        /// <summary>
        /// 更新阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作, 不处理附件和引用集合
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static Task<TEntitySet> UpdateToSaveChangeAsync<TEntitySet>(this DbContext context, TEntitySet entitySet, string user = null) where TEntitySet : class, IEntitySet<string> => context.UpdateToSaveChangeAsync<TEntitySet, string>(entitySet, user);
        /// <summary>
        /// 删除记录并提交保存, 不处理附件和引用集合
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <returns></returns>
        public static Task<HttpStatusCode> DeleteToSaveChangeAsync<TEntitySet>(this DbContext context, TEntitySet entitySet) where TEntitySet : class, IEntitySet<string> => context.DeleteToSaveChangeAsync<TEntitySet, string>(entitySet);
        /// <summary>
        /// 批量新增或修改数据对象模型到当前上下文, 但不提交
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="context"></param>
        /// <param name="user">当前用户</param>
        /// <param name="entitySets"></param>
        /// <returns></returns>
        public static Task AddOrUpdateAsync<TEntitySet>(this DbContext context, string user = null, params TEntitySet[] entitySets) where TEntitySet : class, IEntitySet<string> => context.AddOrUpdateAsync<TEntitySet, string>(user, entitySets);

        /// <summary>
        /// 新增 TEntitySet 对象中 TMember 类型的集合对象
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <typeparam name="TMember"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <param name="collection"></param>
        /// <param name="user">当前用户</param>
        public static Task AddCollectionAsync<TEntitySet, TMember>(this DbContext context, TEntitySet entitySet, IEnumerable<TMember> collection, string user = null) where TEntitySet : class, IEntitySet<string> where TMember : class, IEntitySet<string> => context.AddCollectionAsync<TEntitySet, TMember, string>(entitySet, collection, user);
        /// <summary>
        /// 更新 TEntitySet 对象中 TMember 类型的集合引用
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <typeparam name="TMember"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <param name="collection"></param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static Task UpdateCollectionAsync<TEntitySet, TMember>(this DbContext context, TEntitySet entitySet, IEnumerable<TMember> collection, string user = null) where TEntitySet : class, IEntitySet<string> where TMember : class, IEntitySet<string> => context.UpdateCollectionAsync<TEntitySet, TMember, string>(entitySet, collection, user);

        /// <summary>
        /// 新增 TEntitySet 对象的附加信息
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <typeparam name="TAdditional"></typeparam>
        /// <param name="context"></param>
        /// <param name="additionals"></param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static Task AddAdditionalsAsync<TEntitySet, TAdditional>(this DbContext context, TEntitySet entitySet, IEnumerable<TAdditional> additionals, string user = null) where TEntitySet : class, IEntitySet<string> where TAdditional : class, IAdditional<string> => context.AddAdditionalsAsync<TEntitySet, TAdditional, string>(entitySet, additionals, user);
        /// <summary>
        /// 更新 TEntitySet 对象的附加信息
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <typeparam name="TAdditional"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <param name="additionals"></param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static Task UpdateAdditionalsAsync<TEntitySet, TAdditional>(this DbContext context, TEntitySet entitySet, IEnumerable<TAdditional> additionals, string user = null) where TEntitySet : class, IEntitySet<string> where TAdditional : class, IAdditional<string> => context.UpdateAdditionalsAsync<TEntitySet, TAdditional, string>(entitySet, additionals, user);

        /// <summary>
        /// 字符串类型主键的 Attachment 新增方法
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <param name="attachments"></param>
        /// <param name="user">当前用户</param>
        /// <param name="businessType"></param>
        public static Task AddAttachmentsAsync<TEntitySet>(this DbContext context, TEntitySet entitySet, IEnumerable<Attachment> attachments, string user = null, string businessType = null) where TEntitySet : class, IEntitySet<string> => context.AddAttachmentsAsync<TEntitySet, Attachment, string>(entitySet, attachments, user, businessType);
        /// <summary>
        /// 修改附件集合字符串主键的默认实现, 保留旧附件, 新增新附件, 返回待删除文件列表
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <param name="attachments"></param>
        /// <param name="user">当前用户</param>
        /// <param name="businessType"></param>
        /// <returns></returns>
        public static Task<IEnumerable<string>> UpdateAttachmentsAsync<TEntitySet>(this DbContext context, TEntitySet entitySet, IEnumerable<Attachment> attachments, string user = null, string businessType = null) where TEntitySet : class, IEntitySet<string> => context.UpdateAttachmentsAsync<TEntitySet, Attachment, string>(entitySet, attachments, user, businessType);
        /// <summary>
        /// 自动新增附件字符串主键的默认实现, 方法会自动处理业务对象中的所有附件
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <param name="user">当前用户</param>
        public static Task AddAttachmentsAsync<TEntitySet>(this DbContext context, TEntitySet entitySet, string user = null) where TEntitySet : class, IEntitySet<string> => context.AddAttachmentsAsync<TEntitySet, Attachment, string>(entitySet, user);
        /// <summary>
        /// 自动处理附件字符串主键的默认实现, 方法会自动处理业务对象中的所有附件, 返回待删除文件列表
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static Task<IEnumerable<string>> UpdateAttachmentsAsync<TEntitySet>(this DbContext context, TEntitySet entitySet, string user = null) where TEntitySet : class, IEntitySet<string> => context.UpdateAttachmentsAsync<TEntitySet, Attachment, string>(entitySet, user);
        /// <summary>
        /// 自动删除附件字符串主键的默认实现, 返回待删除文件列表, 适用于实体对象模型从数据库删除时查找原始附件并自动删除
        /// 处理逻辑详见泛型方法
        /// </summary>
        /// <param name="context"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public static Task<IEnumerable<string>> DeleteAttachmentsAsync(this DbContext context, string id) => context.DeleteAttachmentsAsync<Attachment, string>(id);
        #endregion

        #region 基础数据操作方法
        /// <summary>
        /// 新增阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <returns></returns>
        public static async Task UniqueAsync<TEntitySet, TKey>(this DbContext context, TEntitySet entitySet) where TEntitySet : class, IEntitySet<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            var type = typeof(TEntitySet);
            var typeName = type.GetDisplayName();
            var propertyInfos = type.GetProperties().Where(x => x.HasAttribute<UniqueAttribute>());
            if (propertyInfos.IsNotNullOrEmpty())
            {
                var index = 0;
                var keys = new List<string>();
                var values = new List<object>();
                var messages = new List<string>();
                if (entitySet.Id != null && !entitySet.Id.Equals(default))
                {
                    keys.Add($"{nameof(entitySet.Id)} != @{index++}");
                    values.Add($"{entitySet.Id}");
                }

                foreach (var propertyInfo in propertyInfos)
                {
                    keys.Add($"{propertyInfo.Name} == @{index++}");
                    values.Add($"{propertyInfo.GetValue(entitySet)}");
                    messages.Add($"<{propertyInfo.GetDisplayName()}> = {propertyInfo.GetValue(entitySet)}");
                }

                var predicate = string.Join(" and ", keys);
                var exist = await context.Set<TEntitySet>().AnyAsync(predicate, values.ToArray());
                if (exist)
                {
                    throw new Exception($"{typeName}中 {string.Join(", ", messages)} 的记录已经存在!");
                }
            }
        }

        /// <summary>
        /// 单个数据对象的新增, 自动验证重复, 但不处理附件和引用集合
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static async Task<TEntitySet> AddToSaveChangeAsync<TEntitySet, TKey>(this DbContext context, TEntitySet entitySet, string user = null) where TEntitySet : class, IEntitySet<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            await context.UniqueAsync<TEntitySet, TKey>(entitySet);
            if (entitySet is IEntitySetWithCreate<TKey> entitySetWithCreate)
            {
                entitySetWithCreate.Create(user);
            }

            if (entitySet is IEntitySetWithCreateAndUpdate<TKey> entitySetWithCreateAndUpdate)
            {
                entitySetWithCreateAndUpdate.Update(user);
            }

            context.Add(entitySet);
            await context.SaveChangesAsync().ConfigureAwait(true);
            return entitySet;
        }
        /// <summary>
        /// 更新阶段判断满足条件的数据是否存在, 存在则抛出异常, 不存在则执行基类预定义赋值操作, 不处理附件和引用集合
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static async Task<TEntitySet> UpdateToSaveChangeAsync<TEntitySet, TKey>(this DbContext context, TEntitySet entitySet, string user = null) where TEntitySet : class, IEntitySet<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            var displayName = typeof(TEntitySet).GetDisplayName();
            await context.UniqueAsync<TEntitySet, TKey>(entitySet);

            var entity = await context.Set<TEntitySet>().FindAsync(entitySet.Id);
            if (entity == null)
            {
                throw new Exception($"{displayName} 中当前记录不存在!");
            }

            var config = new TypeAdapterConfig();
            config.ForType<TEntitySet, TEntitySet>().Ignore(x => x.Id);
            if (entitySet is IEntitySetWithCreate<TKey>)
            {
                config.ForType<TEntitySet, TEntitySet>().Ignore(x => (x as IEntitySetWithCreate<TKey>).RowVersion);
            }

            entitySet.Adapt(entity, config);

            if (entitySet is IEntitySetWithCreateAndUpdate<TKey> entitySetWithCreateAndUpdate)
            {
                entitySetWithCreateAndUpdate.Update(user);
            }

            context.Update(entity);
            await context.SaveChangesAsync().ConfigureAwait(true);
            return entity;
        }
        /// <summary>
        /// 删除记录并提交保存, 不处理附件和引用集合
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <returns></returns>
        public static async Task<HttpStatusCode> DeleteToSaveChangeAsync<TEntitySet, TKey>(this DbContext context, TEntitySet entitySet) where TEntitySet : class, IEntitySet<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            context.Set<TEntitySet>().Remove(entitySet);
            await context.SaveChangesAsync().ConfigureAwait(true);
            return HttpStatusCode.OK;
        }

        /// <summary>
        /// 批量新增或修改数据对象模型到当前上下文, 但不提交
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="user">当前用户</param>
        /// <param name="entitySets">业务数据</param>
        /// <returns></returns>
        public static async Task AddOrUpdateAsync<TEntitySet, TKey>(this DbContext context, string user = null, params TEntitySet[] entitySets) where TEntitySet : class, IEntitySet<TKey>
        {
            ThrowWhenNull(context, nameof(context));
            if (entitySets.IsNotNullOrEmpty())
            {
                var ids = entitySets.Select(x => x.Id).Distinct();
                var entities = await context.GetQueryable<TEntitySet>().Where(x => ids.Contains(x.Id)).ToListAsync();
                foreach (var entitySet in entitySets)
                {
                    await context.UniqueAsync<TEntitySet, TKey>(entitySet);
                    if (entitySet is IEntitySetWithCreate<TKey> entitySetWithCreate)
                    {
                        entitySetWithCreate.Create(user);
                    }

                    if (entitySet is IEntitySetWithCreateAndUpdate<TKey> entitySetWithCreateAndUpdate)
                    {
                        entitySetWithCreateAndUpdate.Update(user);
                    }

                    var entity = entities.FirstOrDefault(x => x.Id.Equals(entitySet.Id));
                    if (entity == null)
                    {
                        context.Add(entitySet);
                    }
                    else
                    {
                        context.Update(entity.Merge(entitySet));
                    }
                }
            }
        }
        #endregion

        #region 处理对象中的有引用关系的集合
        /// <summary>
        /// 新增 TEntitySet 对象中 TMember 类型的集合对象
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TMember">集合成员对象类型, TMember 类型是包含 TEntitySet 外键的类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <param name="collection">引用数据集合</param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static async Task AddCollectionAsync<TEntitySet, TMember, TKey>(this DbContext context, TEntitySet entitySet, IEnumerable<TMember> collection, string user = null) where TEntitySet : class, IEntitySet<TKey> where TMember : class, IEntitySet<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            if (collection.IsNotNullOrEmpty())
            {
                var foreignKey = typeof(TMember).GetForeignKey<TEntitySet, TKey>();
                if (foreignKey.IsNotNullOrEmpty())
                {
                    user ??= GetUserFromEntitySet<TEntitySet, TKey>(entitySet);
                    foreach (var entity in collection)
                    {
                        entity.SetPropertyValue(foreignKey, entitySet.Id);
                        if (entity is IEntitySetWithCreate<TKey> entityWithCreate)
                        {
                            entityWithCreate.Create(user);
                        }

                        if (entity is IEntitySetWithCreateAndUpdate<TKey> entityWithCreateAndUpdate)
                        {
                            entityWithCreateAndUpdate.Update(user);
                        }

                        await context.AddAsync(entity);
                    }
                }
            }
        }
        /// <summary>
        /// 更新 TEntitySet 对象中 TMember 类型的集合引用
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TMember">集合成员对象类型, TMember 类型是包含 TEntitySet 外键的类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <param name="collection">引用数据集合</param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static async Task UpdateCollectionAsync<TEntitySet, TMember, TKey>(this DbContext context, TEntitySet entitySet, IEnumerable<TMember> collection, string user = null) where TEntitySet : class, IEntitySet<TKey> where TMember : class, IEntitySet<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            var foreignKey = typeof(TMember).GetForeignKey<TEntitySet, TKey>();
            if (foreignKey.IsNotNullOrEmpty())
            {
                var config = new TypeAdapterConfig();
                config.ForType<TEntitySet, TEntitySet>().Ignore(x => x.Id);
                if (entitySet is IEntitySetWithCreate<TKey>)
                {
                    config.ForType<TEntitySet, TEntitySet>().Ignore(x => (x as IEntitySetWithCreate<TKey>).RowVersion, x => (x as IEntitySetWithCreate<TKey>).CreatedBy, x => (x as IEntitySetWithCreate<TKey>).CreatedOn);
                }

                user ??= GetUserFromEntitySet<TEntitySet, TKey>(entitySet);
                var news = collection == null ? new List<TMember>() : collection.ToList();
                var olds = await context.GetQueryable<TMember>().Where(x => x.Id.Equals(entitySet.Id)).ToListAsync();
                foreach (var entity in news)
                {
                    var old = olds.FirstOrDefault(x => x.Id.Equals(entity.Id));
                    if (old == null)
                    {
                        //新的有旧的没有, 则新增
                        entity.SetPropertyValue(foreignKey, entitySet.Id);
                        if (entity is IEntitySetWithCreate<TKey> entityWithCreate)
                        {
                            entityWithCreate.Create(user);
                        }

                        if (entity is IEntitySetWithCreateAndUpdate<TKey> entityWithCreateAndUpdate)
                        {
                            entityWithCreateAndUpdate.Update(user);
                        }

                        context.Add(entity);
                    }
                    else
                    {
                        //新的有旧的也有, 则更新
                        entity.Adapt(old, config);
                        if (old is IEntitySetWithCreateAndUpdate<TKey> entityWithCreateAndUpdate)
                        {
                            entityWithCreateAndUpdate.Update(user);
                        }

                        context.Update(old);
                    }
                }

                foreach (var old in olds)
                {
                    if (!news.Any(x => x.Id.Equals(old.Id)))
                    {
                        context.Remove(old);
                    }
                }
            }
        }
        #endregion

        #region 处理对象中的附加数据
        /// <summary>
        /// 新增 TEntitySet 对象的附加信息
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TAdditional">附加信息集合</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <param name="additionals">附加数据集合</param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static async Task AddAdditionalsAsync<TEntitySet, TAdditional, TKey>(this DbContext context, TEntitySet entitySet, IEnumerable<TAdditional> additionals, string user = null) where TEntitySet : class, IEntitySet<TKey> where TAdditional : class, IAdditional<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            if (additionals.IsNotNullOrEmpty())
            {
                user ??= GetUserFromEntitySet<TEntitySet, TKey>(entitySet);
                foreach (var entity in additionals)
                {
                    entity.AdditionalId = entitySet.Id;
                    entity.Create(user);
                    await context.AddAsync(entity);
                }
            }
        }
        /// <summary>
        /// 更新 TEntitySet 对象的附加信息
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TAdditional">附加信息集合</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <param name="additionals">附加数据集合</param>
        /// <param name="user">当前用户</param>
        /// <returns></returns>
        public static async Task UpdateAdditionalsAsync<TEntitySet, TAdditional, TKey>(this DbContext context, TEntitySet entitySet, IEnumerable<TAdditional> additionals, string user = null) where TEntitySet : class, IEntitySet<TKey> where TAdditional : class, IAdditional<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            user ??= GetUserFromEntitySet<TEntitySet, TKey>(entitySet);
            var news = additionals == null ? new List<TAdditional>() : additionals.ToList();
            var olds = await context.GetQueryable<TAdditional>().Where(x => x.AdditionalId.Equals(entitySet.Id)).ToListAsync();
            foreach (var entity in news)
            {
                var old = olds.FirstOrDefault(x => x.Id.Equals(entity.Id));
                if (old == null)
                {
                    //新的有旧的没有, 则新增
                    entity.AdditionalId = entitySet.Id;
                    entity.Create(user);
                    context.Add(entity);
                }
                else
                {
                    //新的有旧的也有, 则更新
                    old.State = entity.State;
                    old.Index = entity.Index;
                    old.Key = entity.Key;
                    old.Value = entity.Value;
                    context.Update(old);
                }
            }

            foreach (var old in olds)
            {
                if (!news.Any(x => x.Id.Equals(old.Id)))
                {
                    context.Remove(old);
                }
            }
        }
        #endregion

        #region 附件相关处理方法
        /// <summary>
        /// 自动新增附件
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TAttachment">附件数据类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <param name="attachments">新附件集合</param>
        /// <param name="user">当前用户</param>
        /// <param name="businessType">附件业务类型</param>
        /// <returns>待删除文件列表, 请在数据库事物提交成功后删除列表中的文件</returns>
        public static async Task AddAttachmentsAsync<TEntitySet, TAttachment, TKey>(this DbContext context, TEntitySet entitySet, IEnumerable<TAttachment> attachments, string user = null, string businessType = null) where TEntitySet : class, IEntitySet<TKey> where TAttachment : class, IAttachment<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            if (attachments.IsNotNullOrEmpty())
            {
                user ??= GetUserFromEntitySet<TEntitySet, TKey>(entitySet);
                businessType ??= typeof(TEntitySet).Name;
                foreach (var attachment in attachments)
                {
                    //新的有旧的没有, 则新增, 附件只有新增和删除, 没有修改的说法
                    attachment.BusinessKey = entitySet.Id;
                    attachment.BusinessType = businessType;
                    attachment.Create(user);
                    await context.AddAsync(attachment);
                }
            }
        }
        /// <summary>
        /// 修改附件集合, 保留旧附件, 新增新附件, 返回待删除文件列表
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TAttachment">附件数据类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <param name="attachments">新附件集合</param>
        /// <param name="user">当前用户</param>
        /// <param name="businessType">附件业务类型</param>
        /// <returns>待删除文件列表, 请在数据库事物提交成功后删除列表中的文件</returns>
        public static async Task<IEnumerable<string>> UpdateAttachmentsAsync<TEntitySet, TAttachment, TKey>(this DbContext context, TEntitySet entitySet, IEnumerable<TAttachment> attachments, string user = null, string businessType = null) where TEntitySet : class, IEntitySet<TKey> where TAttachment : class, IAttachment<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            user ??= GetUserFromEntitySet<TEntitySet, TKey>(entitySet);
            businessType ??= typeof(TEntitySet).Name;
            var news = attachments == null ? new List<TAttachment>() : attachments.ToList();
            var olds = await context.GetQueryable<TAttachment>().Where(x => x.BusinessKey.Equals(entitySet.Id) && x.BusinessType == businessType).ToListAsync();
            foreach (var attachment in news)
            {
                if (!olds.Any(x => (x.Id != null && x.Path != null && x.Id.Equals(attachment.Id)) || x.Path == attachment.Path))
                {
                    //新的有旧的没有, 则新增, 附件只有新增和删除, 没有修改的说法
                    attachment.BusinessKey = entitySet.Id;
                    attachment.BusinessType = businessType;
                    attachment.Create(user);
                    context.Add(attachment);
                }
            }

            var files = new List<string>();
            foreach (var attachment in olds)
            {
                //旧的有新的没有则删除
                if (!news.Any(x => (x.Id != null && attachment.Id != null && x.Id.Equals(attachment.Id)) || x.Path == attachment.Path))
                {
                    files.Add(attachment.Path);
                    context.Remove(attachment);
                }
            }

            return files;
        }

        /// <summary>
        /// 自动新增附件, 方法会自动处理业务对象中的所有附件
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TAttachment">附件数据类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <param name="user">当前用户</param>
        /// <returns>待删除文件列表, 请在数据库事物提交成功后删除列表中的文件</returns>
        public static async Task AddAttachmentsAsync<TEntitySet, TAttachment, TKey>(this DbContext context, TEntitySet entitySet, string user = null) where TEntitySet : class, IEntitySet<TKey> where TAttachment : class, IAttachment<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            var type = typeof(TEntitySet);
            var attachments = entitySet.GetPropertyValue(AppSettings.DEFAULT_ATTACHMENT_NAME) as IEnumerable<TAttachment>;
            var propertyInfos = type.GetProperties().Where(x => x.HasAttribute<BusinessTypeAttribute>());
            if (propertyInfos.IsNullOrEmpty())
            {
                user ??= GetUserFromEntitySet<TEntitySet, TKey>(entitySet);
                foreach (var propertyInfo in propertyInfos)
                {
                    //如果有 BusinessTypeAttribute 存在, 则要么是单体附件, 要么是自定义附件
                    var businessType = propertyInfo.GetAttributeValue<BusinessTypeAttribute, string>(nameof(BusinessTypeAttribute.Name)) ?? type.Name;
                    if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericType().IsFromType<TAttachment>())
                    {
                        //如果存在 businessType 对应的附件集合
                        await context.AddAttachmentsAsync<TEntitySet, TAttachment, TKey>(entitySet, propertyInfo.GetValue(entitySet) as IEnumerable<TAttachment>, businessType);
                    }
                    else
                    {
                        //不存在则认为是单体附件, 处理方法: 从 Attachments 集合中取出第一个 BusinessType = 当前 businessType 的附件新增到上下文
                        var value = propertyInfo.GetValue(entitySet);
                        if (value != null && value is string path && attachments.IsNotNullOrEmpty())
                        {
                            var attachment = attachments.FirstOrDefault(x => x.BusinessType == businessType && x.Path == path);
                            if (attachment != null)
                            {
                                attachment.BusinessKey = entitySet.Id;
                                attachment.BusinessType = businessType;
                                attachment.Create(user);
                                context.Add(attachment);
                            }
                        }
                    }
                }
            }
            else
            {
                //如果没有 BusinessTypeAttribute 存在, 则认为基类定义的 Attachments 是当前对象的唯一附件集合, BusinessType 默认取值当前 entityset type name
                await context.AddAttachmentsAsync<TEntitySet, TAttachment, TKey>(entitySet, attachments, type.Name);
            }
        }
        /// <summary>
        /// 自动处理附件, 方法会自动处理业务对象中的所有附件, 返回待删除文件列表
        /// 附件分两种, 一种是单个附件, 字段值只记录了附件的 Path 值, 真正的附件对象在基类的 Attachments 中
        /// 第二种是通用附件, 某个对象的附件列表, 比如审核资料, 需定义新的 Attachment 集合并指定 BusinessType 特性
        /// 通用附件如果未指定集合和 BusinessType, 则认为单个附件不存在且通用附件集合只有一个默认的 BusinessType 为当前数据对象模型的类型名称, 而附件集合直接使用基类成员 Attachments
        /// </summary>
        /// <typeparam name="TEntitySet">业务数据类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="entitySet">业务数据</param>
        /// <param name="user">当前用户</param>
        /// <returns>待删除文件列表, 请在数据库事物提交成功后删除列表中的文件</returns>
        public static async Task<IEnumerable<string>> UpdateAttachmentsAsync<TEntitySet, TAttachment, TKey>(this DbContext context, TEntitySet entitySet, string user = null) where TEntitySet : class, IEntitySet<TKey> where TAttachment : class, IAttachment<TKey>
        {
            ValidateParameters<TEntitySet, TKey>(context, entitySet);
            user ??= GetUserFromEntitySet<TEntitySet, TKey>(entitySet);
            var files = new List<string>();
            var type = typeof(TEntitySet);
            var oldAttachments = await context.GetQueryable<TAttachment>().Where(x => x.BusinessKey.Equals(entitySet.Id)).ToListAsync();
            var propertyInfos = type.GetProperties().Where(x => x.HasAttribute<BusinessTypeAttribute>()).ToList();
            if (propertyInfos.IsNotNullOrEmpty())
            {
                //如果有 BusinessTypeAttribute 存在, 则要么是单体附件, 要么是自定义附件
                foreach (var propertyInfo in propertyInfos)
                {
                    var businessType = propertyInfo.GetAttributeValue<BusinessTypeAttribute, string>(nameof(BusinessTypeAttribute.Name)) ?? type.Name;
                    if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericType().IsFromType<IAttachment<TKey>>())
                    {
                        //附件集合
                        var news = (propertyInfo.GetValue(entitySet) as IEnumerable<IAttachment<TKey>>) ?? new List<IAttachment<TKey>>();
                        var olds = oldAttachments.Where(x => x.BusinessType == businessType);
                        foreach (var attachment in news)
                        {
                            if (!olds.Any(x => (x.Id != null && attachment.Id != null && x.Id.Equals(attachment.Id)) || x.Path == attachment.Path))
                            {
                                //旧的没有新的有, 则新增
                                attachment.BusinessKey = entitySet.Id;
                                attachment.BusinessType = businessType;
                                attachment.Create(user);
                                context.Add(attachment);
                            }
                        }

                        foreach (var attachment in olds)
                        {
                            if (!news.Any(x => (x.Id != null && attachment.Id != null && x.Id.Equals(attachment.Id)) || x.Path == attachment.Path))
                            {
                                //新的没有旧的有, 则删除
                                files.Add(attachment.Path);
                                context.Remove(attachment);
                            }
                        }
                    }
                    else
                    {
                        //单体附件, 处理方法: 如果 olds 中存在 Path = 当前值的, 则附件没有变
                        //当前值为空, 则有可能删除了附件, 从 olds 中删除所有 BusinessType = 当前 BusinessType 的附件
                        //否则从 Attachments 中取出第一个 BusinessType = 当前 BusinessType 的附件新增到上下文, 并从 olds 中删除所有 BusinessType = 当前 BusinessType 的附件
                        var value = propertyInfo.GetValue(entitySet);
                        var olds = oldAttachments.Where(x => x.BusinessType == businessType);
                        if (value == null && olds.IsNotNullOrEmpty())
                        {
                            //附件变了, 没上传, 仅仅删除了附件
                            foreach (var attachment in olds)
                            {
                                files.Add(attachment.Path);
                                context.Remove(attachment);
                            }
                        }

                        var news = entitySet.GetPropertyValue(AppSettings.DEFAULT_ATTACHMENT_NAME) as IEnumerable<IAttachment<TKey>>;
                        if (value != null && value is string path && news.IsNotNullOrEmpty() && news.Any(x => x.BusinessType == businessType && x.Path == path))
                        {
                            //附件变了,有新附件
                            if (olds.IsNullOrEmpty())
                            {
                                //无旧附件, 仅仅新增了附件
                                var attachment = news.FirstOrDefault(x => x.BusinessType == businessType && x.Path == path);
                                attachment.BusinessKey = entitySet.Id;
                                attachment.BusinessType = businessType;
                                attachment.Create(user);
                                context.Add(attachment);
                            }

                            if (olds.IsNotNullOrEmpty() && !olds.Any(x => x.Path == path))
                            {
                                //既有旧附件又有新附件, 且旧附件地址不等于新附件地址, 则删除旧附件, 新增新附件
                                foreach (var attachment in olds)
                                {
                                    files.Add(attachment.Path);
                                    context.Remove(attachment);
                                }

                                var newAttachment = news.FirstOrDefault(x => x.BusinessType == businessType && x.Path == path);
                                newAttachment.BusinessKey = entitySet.Id;
                                newAttachment.BusinessType = businessType;
                                newAttachment.Create(user);
                                context.Add(newAttachment);
                            }
                        }
                    }
                }
            }
            else
            {
                //如果没有, 则认为基类定义的 Attachments 是当前对象的唯一附件集合, BusinessType 默认取值当前 entityset type name
                var news = (entitySet.GetPropertyValue(AppSettings.DEFAULT_ATTACHMENT_NAME) as IEnumerable<IAttachment<TKey>>) ?? new List<IAttachment<TKey>>();
                foreach (var attachment in news)
                {
                    if (!oldAttachments.Any(x => x.Id.Equals(attachment.Id) || x.Path == attachment.Path))
                    {
                        //旧的没有新的有, 则新增
                        attachment.BusinessKey = entitySet.Id;
                        attachment.BusinessType = type.Name;
                        attachment.Create(user);
                        context.Add(attachment);
                    }
                }

                foreach (var attachment in oldAttachments)
                {
                    if (!news.Any(x => x.Id.Equals(attachment.Id) || x.Path == attachment.Path))
                    {
                        //新的没有旧的有, 则删除
                        files.Add(attachment.Path);
                        context.Remove(attachment);
                    }
                }
            }

            return files;
        }

        /// <summary>
        /// 自动删除附件, 返回待删除文件列表, 适用于实体对象模型从数据库删除时查找原始附件并自动删除
        /// </summary>
        /// <typeparam name="TAttachment">附件数据类型</typeparam>
        /// <typeparam name="TKey">键类型</typeparam>
        /// <param name="context">数据库上下文</param>
        /// <param name="id">业务数据主键</param>
        /// <returns>待删除文件列表, 请在数据库事物提交成功后删除列表中的文件</returns>
        public static async Task<IEnumerable<string>> DeleteAttachmentsAsync<TAttachment, TKey>(this DbContext context, TKey id) where TAttachment : class, IAttachment<TKey>
        {
            ThrowWhenNull(context, nameof(context));
            var attachments = await context.GetQueryable<TAttachment>().Where(x => x.BusinessKey.Equals(id)).ToListAsync();
            var files = attachments.Select(x => x.Path);
            context.RemoveRange(attachments);
            return files;
        }
        #endregion

        #region private methods
        /// <summary>
        /// 验证实体方法参数
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <param name="context"></param>
        /// <param name="entitySet"></param>
        private static void ValidateParameters<TEntitySet, TKey>(DbContext context, TEntitySet entitySet) where TEntitySet : class, IEntitySet<TKey>
        {
            var arg = typeof(TEntitySet).GetDisplayName();
            ThrowWhenNull(context, nameof(context));
            ThrowWhenNull(entitySet, arg);
            ThrowWhenNullOrDefault(entitySet.Id, arg);
        }
        /// <summary>
        /// 根据当前使用的实体获取操作人
        /// </summary>
        /// <typeparam name="TEntitySet"></typeparam>
        /// <typeparam name="TKey"></typeparam>
        /// <param name="entitySet"></param>
        /// <returns></returns>
        private static string GetUserFromEntitySet<TEntitySet, TKey>(TEntitySet entitySet) where TEntitySet : class, IEntitySet<TKey>
        {
            if (entitySet is IEntitySetWithCreate<TKey> entitySetWithCreate)
            {
                return entitySetWithCreate.CreatedBy;
            }

            if (entitySet is IEntitySetWithCreateAndUpdate<TKey> entitySetWithCreateAndUpdate)
            {
                return entitySetWithCreateAndUpdate.UpdatedBy;
            }

            return null;
        }
        /// <summary>
        /// get filter expressions logic
        /// </summary>
        /// <param name="logicType"></param>
        /// <returns></returns>
        private static string GetFilterLogic(LogicType logicType) => logicType switch
        {
            LogicType.And => " && ",
            LogicType.Or => " || ",
            _ => throw new ArgumentException($"{nameof(logicType)} was not support"),
        };
        /// <summary>
        /// get expression from filter option
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="filter"></param>
        /// <returns></returns>
        private static string GetFilterExpression<T>(FilterOption<T> filter) => filter.Operator switch
        {
            OperatorType.Equal => $"{filter.Field} == {GetFilterValue(filter)}",
            OperatorType.NotEqual => $"{filter.Field} != {GetFilterValue(filter)}",
            OperatorType.GreaterThan => $"{filter.Field} > {filter.FieldValue}",
            OperatorType.GreaterThanAndEqual => $"{filter.Field} >= {filter.FieldValue}",
            OperatorType.LessThan => $"{filter.Field} < {filter.FieldValue}",
            OperatorType.LessThanAndEqual => $"{filter.Field} <= {filter.FieldValue}",
            OperatorType.Contains => $"{filter.Field}.Contains({GetFilterValue(filter)})",
            OperatorType.StartsWith => $"{filter.Field}.StartsWith({GetFilterValue(filter)})",
            OperatorType.EndsWith => $"{filter.Field}.EndsWith({GetFilterValue(filter)})",
            _ => throw new ArgumentException($"{nameof(filter.Field)} was not support the operation {filter.Operator}"),
        };
        private static object GetFilterValue<T>(FilterOption<T> filter) => filter.FieldType == typeof(string) ? $"\"{filter.FieldValue}\"" : filter.FieldValue;
        #endregion
    }
}
